(* open IntInf; *)

fun compose a b =
    (fn input => b (a input));

(* fun chain procs = *)
(*     foldl (fn (proc, composed_procs) => *)
(*               (fn input => *)
(*                   proc (composed_procs input))) *)
(*           (fn any => any) *)
(*           procs; *)


val block_tokenizer = StringExtra.split "\n\n";
val newline_tokenizer = String.tokens (fn c => c = #"\n");

val str = FileExtra.read "input";
val parts = block_tokenizer str;
val number_sequence =
    map (fn num_as_str =>
            case Int.fromString num_as_str of
                SOME number => number
              | NONE =>
                raise Fail "encountered a non-int string")
        (String.tokens (fn c => c = #",") (hd parts));
val board_strings = tl parts;
val boards = map Board.fromString board_strings;
val first_board = hd boards;

val rows_and_cols = ListExtra.flatten (map Board.getAllRowsAndColumns boards);

val seq_len = List.length number_sequence;


fun find_board (nil, number_sequence) = NONE
  | find_board (boards as (board::rest), number_sequence) =
    if Board.has_completed_line (board, number_sequence)
    then
        SOME board
    else
        find_board (rest, number_sequence);


fun calculate_score (board, num_seq) =
    let
        val finishing_num = List.last num_seq;
        val board_score = Board.sum_unselected (board, num_seq);
        val _ = print ("finishing num: " ^ (Int.toString finishing_num) ^ "\n");
        val _ = print ("board score: " ^ (Int.toString board_score) ^ "\n");
    in
        board_score * finishing_num
    end;



fun try_sequences (boards, num_sequence) =
    let
        val max_prefix_len = List.length num_sequence;
    in
        let
            fun iter (nil, prefix_len) = raise Fail "no more boards"
              | iter ([board], prefix_len) =
                let
                    val prefix = List.take (num_sequence, prefix_len);
                in
                    (* repeat until the last board was solved *)
                    case find_board ([board], prefix) of
                        SOME board => calculate_score (board, prefix)
                      | NONE => iter ([board], (prefix_len + 1))
                end
              | iter (remaining_boards, prefix_len) =
                if prefix_len < max_prefix_len
                then
                    let
                        val prefix = List.take (num_sequence, prefix_len);
                        val _ = print ("prefix length: " ^ (Int.toString prefix_len) ^ "\n");
                        val _ = print ("boards remaining: " ^ (Int.toString (length remaining_boards)) ^ "\n");
                        val result_for_prefix = find_board (remaining_boards, prefix);
                    in
                        case result_for_prefix of
                            (* try with the board removed *)
                            SOME winning_board => iter ((ListExtra.remove remaining_boards winning_board),
                                                        prefix_len)
                          | NONE => iter (remaining_boards, prefix_len + 1)
                    end
                else
                    raise Fail "no winning board for any prefix of number sequence";
        in
            iter (boards, 0)
        end
    end;


val solution = try_sequences (boards, number_sequence);
solution;

(* val result = Board.sum_unselected (hd boards) [50,98,65,14,47,40,84,0,37]; *)
